/*
 * Ingenic libsysutils Audio encode and decode implement.
 *
 * Copyright (C) 2017 Ingenic Semiconductor Co.,Ltd
 * Author: Lionel <shunxi.zhang@ingenic.com>
 */

#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <stdint.h>
#include <errno.h>
#include <pthread.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/time.h>

#include <imp/imp_audio.h>
#include <imp/imp_log.h>

#include "codecs/g711-code/g711codec.h"
#include "codecs/adpcm-code/adpcm.h"
#include "codecs/g726-code/g726codec.h"
#include "audio_common.h"
#include "abuf.h"
#ifdef DMALLOC
#include <imp/dmalloc.h>
#endif
#define  TAG "aenc"

static inline void _aenc_chn_lock(pthread_mutex_t *mutex)
{
	pthread_mutex_lock(mutex);
}

static inline void _aenc_chn_unlock(pthread_mutex_t *mutex)
{
	pthread_mutex_unlock(mutex);
}

int _aenc_pcm2g711a(char *InAudioData, char *OutAudioData, int DataLen)
{
	int Retaen = 0;

	if((NULL == InAudioData) && (NULL == OutAudioData) && (0 == DataLen)) {
		IMP_LOG_ERR(TAG, "Error, empty data or transmit failed, exit !\n");
		return -1;
	}

	Retaen = g711a_encode((unsigned char *)OutAudioData, (short*)InAudioData, DataLen/2);

	return Retaen;
}

int _aenc_pcm2g711u(char *InAudioData, char *OutAudioData, int DataLen)
{
	int Retuen = 0;

	if((NULL == InAudioData) && (NULL == OutAudioData) && (0 == DataLen)) {
		IMP_LOG_ERR(TAG, "Error, empty data or transmit failed, exit !\n");
		return -1;
	}

	Retuen = g711u_encode((unsigned char *)OutAudioData, (short*)InAudioData, DataLen/2);

	return Retuen;
}

int _aenc_pcm2adpcm(char *InAudioData,char *OutAudioData,int DataLen)
{
	int Retude = 0;
	if((NULL == InAudioData)&&(NULL == OutAudioData) && (0 == DataLen)){
		IMP_LOG_ERR(TAG,"Error,empty data or trasmit failed,exit !\n");
		return -1;
	}

	static adpcm_state state;
	Retude = adpcm_coder((short *)InAudioData,(char*)OutAudioData,DataLen/2,&state);

	return Retude;
}

#define G726_BIT_RATE 4
int _aenc_pcm2g726(char *InAudioData,char *OutAudioData,int DataLen)
{
	int Retude = 0;
	if ((NULL == InAudioData) && (NULL == OutAudioData) && (0 == DataLen)) {
		IMP_LOG_ERR(TAG,"Error,empty data or transmit failed,exit!\n");
		return -1;
	}

	static int tmpCount = 0;
	static g726_state_t g726_state;

	if (0 == tmpCount) {
		g726_init(&g726_state,8000*G726_BIT_RATE);
		tmpCount ++;
	}
	Retude = g726_encode(&g726_state,(unsigned char*)OutAudioData,(short*)InAudioData,DataLen/2);

	return Retude;
}

/* Audio Encoder API */
#define MAX_AUDIO_ENC_CHN 5
#define MAX_AUDIO_ENC_ENCODER 5

typedef struct {
	int chnID;
	int chnEnable;
	pthread_t chnProcess;
	IMPAudioEncChnAttr chnAttr;
	void *aenc_buf;
	int aenc_buf_size;
	pthread_mutex_t chnLock;
} AudioEncChn;

typedef struct {
	int encoderRegister;
	IMPAudioEncEncoder encoder;
} AudioEncMethod;

static AudioEncChn audioEncChn[MAX_AUDIO_ENC_CHN];
static AudioEncMethod audioEncMethod[PT_MAX + MAX_AUDIO_ENC_ENCODER];

static int _g711a_open_encoder(void *encoderAttr, void *encoder)
{
	return 0;
}

static int _g711a_encode_frm(void *encoder, IMPAudioFrame *data, unsigned char *outbuf,int *outLen)
{
	*outLen = _aenc_pcm2g711a((char *)data->virAddr, (char *)outbuf, data->len);
	return 0;
}

static int _g711a_close_encoder(void *encoder)
{
	return 0;
}

static int _g711u_open_encoder(void *encoderAttr, void *encoder)
{
	return 0;
}

static int _g711u_encode_frm(void *encoder, IMPAudioFrame *data, unsigned char *outbuf,int *outLen)
{
	*outLen = _aenc_pcm2g711u((char *)data->virAddr, (char *)outbuf, data->len);
	return 0;
}

static int _g711u_close_encoder(void *encoder)
{
	return 0;
}

static int _g726_open_encoder(void *encoderAttr, void *encoder)
{
	return 0;
}

static int _g726_encode_frm(void *encoder,IMPAudioFrame *data,unsigned char *outbuf,int *outLen)
{
	if((NULL == data) && (NULL == outbuf) && (0 == outLen)){
		IMP_LOG_ERR(TAG,"Error,empty data or transmit failed,exit !\n");
		return -1;
	}
	*outLen = _aenc_pcm2g726((char *)data->virAddr,(char *)outbuf,data->len);
	return 0;
}

static int _g726_close_encoder(void *encoder)
{
	return 0;
}

static int _adpcm_open_encoder(void *encoderAttr,void *encoder)
{
	return 0;
}

static int _adpcm_encode_frm(void *encoder,IMPAudioFrame *data,unsigned char *outbuf,int *outLen)
{
	if((NULL == data) &&(NULL == outbuf) && (NULL == outLen)){
		IMP_LOG_ERR(TAG, "Error, empty data or transmit failed, exit !\n");
		return -1;
	}

	*outLen = _aenc_pcm2adpcm((char *)data->virAddr, (char *)outbuf, data->len);
	return 0;
}

static int _adpcm_encode_close(void *encoder)
{
	return 0;
}

struct aenc_buf_data {
	void *node;
	int size;
	int data[0];
};

int IMP_AENC_CreateChn(int AeChn, IMPAudioEncChnAttr *attr)
{
	int size = 0;
	if(AeChn > MAX_AUDIO_ENC_CHN) {
		IMP_LOG_ERR(TAG, "Invalid Audio Enc Channel.\n");
		return -1;
	}

	if(audioEncChn[AeChn].chnEnable == 1) {
		IMP_LOG_ERR(TAG, "AeChn is already enabled.\n");
		return -1;
	}

	audioEncChn[AeChn].chnID = AeChn;
	audioEncChn[AeChn].chnAttr.type = attr->type;
	audioEncChn[AeChn].chnAttr.bufSize = attr->bufSize;
	audioEncChn[AeChn].chnAttr.value = attr->value;
	size = _ai_get_buf_size();
	if (0 >= size) {
		IMP_LOG_ERR(TAG, "ai_buf size error %d\n", size);
		size = /* numPerFrm */400*sizeof(short);
	}
	pthread_mutex_init(&audioEncChn[AeChn].chnLock, NULL);
	_aenc_chn_lock(&audioEncChn[AeChn].chnLock);
	if(audioEncChn[AeChn].aenc_buf == NULL) {
		audioEncChn[AeChn].aenc_buf =  audio_buf_alloc(audioEncChn[AeChn].chnAttr.bufSize, size+sizeof(struct aenc_buf_data), 0);
		audioEncChn[AeChn].aenc_buf_size = size;
		if(audioEncChn[AeChn].aenc_buf == NULL) {
			IMP_LOG_ERR(TAG, "aenc_buf alloc error\n");
			_aenc_chn_unlock(&audioEncChn[AeChn].chnLock);
			return -1;
		}
	}
	_aenc_chn_unlock(&audioEncChn[AeChn].chnLock);

	/* PT_G711A */
	audioEncMethod[PT_G711A].encoder.type = PT_G711A;
	audioEncMethod[PT_G711A].encoder.maxFrmLen = 4096;
	sprintf(audioEncMethod[PT_G711A].encoder.name, "%s", "PT_G711A");
	audioEncMethod[PT_G711A].encoder.openEncoder = _g711a_open_encoder;
	audioEncMethod[PT_G711A].encoder.encoderFrm = _g711a_encode_frm;
	audioEncMethod[PT_G711A].encoder.closeEncoder = _g711a_close_encoder;
	audioEncMethod[PT_G711A].encoderRegister = 1;

	/* PT_G711U */
	audioEncMethod[PT_G711U].encoder.type = PT_G711U;
	audioEncMethod[PT_G711U].encoder.maxFrmLen = 4096;
	sprintf(audioEncMethod[PT_G711U].encoder.name, "%s", "PT_G711U");
	audioEncMethod[PT_G711U].encoder.openEncoder = _g711u_open_encoder;
	audioEncMethod[PT_G711U].encoder.encoderFrm = _g711u_encode_frm;
	audioEncMethod[PT_G711U].encoder.closeEncoder = _g711u_close_encoder;
	audioEncMethod[PT_G711U].encoderRegister = 1;

	/* PT_G726 */
	audioEncMethod[PT_G726].encoder.type = PT_G726;
	audioEncMethod[PT_G726].encoder.maxFrmLen = 4096;
	sprintf(audioEncMethod[PT_G726].encoder.name, "%s", "PT_G726");
	audioEncMethod[PT_G726].encoder.openEncoder = _g726_open_encoder;
	audioEncMethod[PT_G726].encoder.encoderFrm = _g726_encode_frm;
	audioEncMethod[PT_G726].encoder.closeEncoder = _g726_close_encoder;
	audioEncMethod[PT_G726].encoderRegister = 1;

	/* PT_ADPCM */
	audioEncMethod[PT_ADPCM].encoder.type = PT_ADPCM;
	audioEncMethod[PT_ADPCM].encoder.maxFrmLen = 4096;
	sprintf(audioEncMethod[PT_ADPCM].encoder.name,"%s","PT_ADPCM");
	audioEncMethod[PT_ADPCM].encoder.openEncoder = _adpcm_open_encoder;
	audioEncMethod[PT_ADPCM].encoder.encoderFrm = _adpcm_encode_frm;
	audioEncMethod[PT_ADPCM].encoder.closeEncoder = _adpcm_encode_close;
	audioEncMethod[PT_ADPCM].encoderRegister = 1;

	if(!audioEncMethod[audioEncChn[AeChn].chnAttr.type].encoderRegister) {
		IMP_LOG_ERR(TAG, "encoder [%d] is not register\n", audioEncChn[AeChn].chnAttr.type);
		return -1;
	}

	if(audioEncMethod[audioEncChn[AeChn].chnAttr.type].encoder.openEncoder)
		audioEncMethod[audioEncChn[AeChn].chnAttr.type].encoder.openEncoder(NULL, NULL);

	audioEncChn[AeChn].chnEnable = 1;
	return 0;
}

int IMP_AENC_DestroyChn(int AeChn)
{
	int ret = -1;
	if (AeChn > MAX_AUDIO_ENC_CHN) {
		IMP_LOG_ERR(TAG, "Invalid Audio Enc Channel.\n");
		return -1;
	}

	if (audioEncMethod[audioEncChn[AeChn].chnAttr.type].encoder.closeEncoder)
		audioEncMethod[audioEncChn[AeChn].chnAttr.type].encoder.closeEncoder(NULL);

	_aenc_chn_lock(&audioEncChn[AeChn].chnLock);
	audioEncChn[AeChn].chnEnable = 0;
	if (audioEncChn[AeChn].aenc_buf != NULL) {
		audio_buf_free(audioEncChn[AeChn].aenc_buf);
		audioEncChn[AeChn].aenc_buf = NULL;
	}
	audioEncChn[AeChn].aenc_buf = NULL;
	_aenc_chn_unlock(&audioEncChn[AeChn].chnLock);
	ret = pthread_mutex_destroy(&audioEncChn[AeChn].chnLock);
	if (0 != ret) {
		IMP_LOG_ERR(TAG, "err: destroy aenc chn lock\n");
		return ret;
	}
	return 0;
}

int IMP_AENC_SendFrame(int AeChn, IMPAudioFrame *frm)
{
	void *buf_node = NULL;
	struct aenc_buf_data *data = NULL;
	if (AeChn > MAX_AUDIO_ENC_CHN) {
		IMP_LOG_ERR(TAG, "Invalid Audio Enc Channel.\n");
		return -1;
	}

	if (audioEncChn[AeChn].chnEnable != 1) {
		IMP_LOG_ERR(TAG, "AeChn is not enabled.\n");
		return -1;
	}

	while (1) {
		_aenc_chn_lock(&audioEncChn[AeChn].chnLock);
		buf_node = audio_buf_get_node(audioEncChn[AeChn].aenc_buf, AUDIO_BUF_EMPTY);
		_aenc_chn_unlock(&audioEncChn[AeChn].chnLock);
		if (buf_node) {
			data = audio_buf_node_get_data(buf_node);
			data->size = audioEncChn[AeChn].aenc_buf_size;
			if (audioEncMethod[audioEncChn[AeChn].chnAttr.type].encoder.encoderFrm) {
			audioEncMethod[audioEncChn[AeChn].chnAttr.type].encoder.encoderFrm(NULL, frm, (void *)data->data, &data->size);
			}
			data->node = buf_node;
			_aenc_chn_lock(&audioEncChn[AeChn].chnLock);
			audio_buf_put_node(audioEncChn[AeChn].aenc_buf, buf_node, AUDIO_BUF_FULL);
			_aenc_chn_unlock(&audioEncChn[AeChn].chnLock);
			break;
		}
		usleep(SLEEP_TIME);
	}
	return 0;
}

int IMP_AENC_PollingStream(int AeChn, unsigned int timeout_ms)
{
	unsigned int time_delay = 0;
	void *buf_node = NULL;
	if (AeChn > MAX_AUDIO_ENC_CHN) {
		IMP_LOG_ERR(TAG, "Invalid Audio Enc Channel.\n");
		return -1;
	}

	if (audioEncChn[AeChn].chnEnable != 1) {
		IMP_LOG_ERR(TAG, "AeChn is not enabled.\n");
		return -1;
	}

	do {
		_aenc_chn_lock(&audioEncChn[AeChn].chnLock);
		buf_node = audio_buf_try_get_node(audioEncChn[AeChn].aenc_buf, AUDIO_BUF_FULL);
		_aenc_chn_unlock(&audioEncChn[AeChn].chnLock);
		if (buf_node != NULL)
			break;

		if (buf_node == NULL && time_delay >= timeout_ms) {
			IMP_LOG_ERR(TAG, "%s %d aenc polling timeout.\n", __func__, __LINE__);
			return -1;
		}
		usleep(20 * 1000);
		time_delay += 20;
	} while(1);

	return 0;
}

int IMP_AENC_GetStream(int AeChn, IMPAudioStream *stream ,IMPBlock block)
{
	void *buf_node = NULL;
	struct aenc_buf_data *data = NULL;
	if (AeChn > MAX_AUDIO_ENC_CHN) {
		IMP_LOG_ERR(TAG, "Invalid Audio Enc Channel.\n");
		return -1;
	}

	if (audioEncChn[AeChn].chnEnable != 1) {
		IMP_LOG_ERR(TAG, "AeChn is not enabled.\n");
		return -1;
	}

	do {
		_aenc_chn_lock(&audioEncChn[AeChn].chnLock);
		buf_node = audio_buf_get_node(audioEncChn[AeChn].aenc_buf, AUDIO_BUF_FULL);
		_aenc_chn_unlock(&audioEncChn[AeChn].chnLock);
		if (buf_node) {
			struct timeval tv = {0};
			data = audio_buf_node_get_data(buf_node);
			stream->stream = (void *)data->data;
			stream->len = data->size;
			gettimeofday(&tv, NULL);
			stream->timeStamp = tv.tv_sec * 1000 * 1000 + tv.tv_usec;
			break;
		}
		usleep(SLEEP_TIME);
	} while (block == BLOCK);

	return 0;
}

int IMP_AENC_ReleaseStream(int AeChn,IMPAudioStream *stream)
{
	void *buf_node = NULL;
	struct aenc_buf_data *buf_data = NULL;
	if (AeChn > MAX_AUDIO_ENC_CHN) {
		IMP_LOG_ERR(TAG, "Invalid Audio Enc Channel.\n");
		return -1;
	}

	if (audioEncChn[AeChn].chnEnable != 1) {
		IMP_LOG_ERR(TAG, "AeChn is not enabled.\n");
		return -1;
	}
	buf_data = container_of((void *)stream->stream, struct aenc_buf_data, data);
	_aenc_chn_lock(&audioEncChn[AeChn].chnLock);
	buf_node = buf_data->node;
	audio_buf_put_node(audioEncChn[AeChn].aenc_buf, buf_node, AUDIO_BUF_EMPTY);
	_aenc_chn_unlock(&audioEncChn[AeChn].chnLock);

	return 0;
}

int IMP_AENC_RegisterEncoder(int *handle, IMPAudioEncEncoder *encoder)
{
	int handle_tmp = PT_MAX;
	for (handle_tmp = PT_MAX; handle_tmp < (PT_MAX + MAX_AUDIO_ENC_ENCODER); handle_tmp++) {
		if (audioEncMethod[handle_tmp].encoderRegister == 0)
			break;
	}

	if (handle_tmp == (PT_MAX + MAX_AUDIO_ENC_ENCODER)) {
		IMP_LOG_ERR(TAG, "You have reached the maximum amount of registration.\n");
		return -1;
	}

	memcpy(&audioEncMethod[handle_tmp].encoder, encoder, sizeof(IMPAudioEncEncoder));
	audioEncMethod[handle_tmp].encoderRegister = 1;
	*handle = handle_tmp;
	return 0;
}


int IMP_AENC_UnRegisterEncoder(int *handle)
{
	if (*handle < PT_MAX || *handle > (PT_MAX + MAX_AUDIO_ENC_ENCODER)) {
		IMP_LOG_ERR(TAG, "Invalid handle\n");
		return -1;
	}

	audioEncMethod[*handle].encoderRegister = 0;
	return 0;
}

#ifdef TEST_AENC
int main()
{
	return 0;
}
#endif
